
// Copyright (C) 2012 Luca Piccioni
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//  
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//  
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Xml;
using Derm.Render;

namespace Derm.Xml.Collada
{
	/// <summary>
	/// 
	/// </summary>
	internal class ColladaCollationContext
	{
		/// <summary>
		/// The element instancing the geometry that defines the collation.
		/// </summary>
		public ColladaInstanceGeometry InstanceGeometry;

		/// <summary>
		/// A set of <see cref="ArrayBufferObject"/> indexed with their identifier.
		/// </summary>
		public Dictionary<string, ArrayBufferObject> ArrayBuffers;
	}

	/// <summary>
	/// Base class for all collation elements.
	/// </summary>
	public abstract class ColladaCollation : ColladaXmlSerializable
	{
		#region Constructors

		/// <summary>
		/// Construct a ColladaCollation.
		/// </summary>
		/// <param name="parent">
		/// The parent element containing this ColladaCollation.
		/// </param>
		protected ColladaCollation(ColladaXmlSerializable parent) : base(parent)
		{
			
		}

		#endregion

		#region Collation Information

		/// <summary>
		/// The name attribute is the text string name of this element. Optional attribute.
		/// </summary>
		public string Name;

		/// <summary>
		/// The count attribute indicates the number of line primitives. Required attribute.
		/// </summary>
		public uint Count;

		/// <summary>
		/// The material attribute declares a symbol for a material. This symbol is bound to a material at 
		/// the time of instantiation. If the material attribute is not specified then the lighting and 
		/// shading results are application defined. Optional attribute.
		/// </summary>
		public string Material;

		/// <summary>
		/// The input element may occur any number of times. This input is a local input with the offset 
		/// and set attributes.
		/// </summary>
		public readonly List<ColladaInputLocalOffset> Inputs = new List<ColladaInputLocalOffset>();

		/// <summary>
		/// The extra element may appear any number of times.
		/// </summary>
		public readonly List<ColladaExtra> Extras = new List<ColladaExtra>(); 

		#endregion

		#region Vertex Array Definition

		/// <summary>
		/// Get a vertex array object corresponding to this collation of geometric elements.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="ColladaCollationContext"/> that specify the parameters available for the collation.
		/// </param>
		/// <returns>
		/// It returns a <see cref="VertexArrayObject"/> containing vertex arrays and elements.
		/// </returns>
		internal abstract VertexArrayObject GetArrayObject(ColladaCollationContext ctx);

		/// <summary>
		/// 
		/// </summary>
		/// <param name="ctx"></param>
		/// <param name="vertexArray"></param>
		internal void CompileVertexArrayState(ColladaCollationContext ctx, VertexArrayObject vertexArray)
		{
			// Collation material
			if (Material != null) {
				ColladaShaderParameters shaderParameters = null;

				sLog.Debug("Compiling collation state with material '{0}'", Material);

				foreach (ColladaBindMaterial bindMaterial in  ctx.InstanceGeometry.BindMaterials) {
					if (bindMaterial.HasMaterial(Material)) {
						shaderParameters = bindMaterial.Create(Material);
						break;
					}
				}

				if (shaderParameters != null) {
					// Setup shader program
					vertexArray.Shader = shaderParameters.ShaderProgram;
					vertexArray.Shader.Create(shaderParameters.ShaderCompiler);
					// Setup render state
					vertexArray.StateSet = shaderParameters.RenderState;
				}
			} else {
				sLog.Debug("Collation has no material defined. Inherith parent material.");
			}
		}

		/// <summary>
		/// Determine whether all inputs are sharing the very same element buffer (indices).
		/// </summary>
		/// <returns>
		/// It returns a boolean value indicating whether all inputs of this collation are sharing the very
		/// same element buffer (i.e. all inputs have the same offset).
		/// </returns>
		protected bool IsSharedElementBuffer()
		{
			return (Inputs.TrueForAll(delegate(ColladaInputLocalOffset input) { return (input.Offset == Inputs[0].Offset); }));
		}

		/// <summary>
		/// Determine whether a set of indices are simple.
		/// </summary>
		/// <param name="indices">
		/// An array of <see cref="System.UInt32"/> that specify the indices.
		/// </param>
		/// <returns>
		/// It returns a boolean value indicating whether <paramref name="indices"/> array is a simple one (i.e.
		/// the <i>i</i>th index has a value of <i>i</i>).
		/// </returns>
		protected bool IsSimpleElementBuffer(uint[] indices)
		{
			if (indices == null)
				throw new ArgumentNullException("indices");

			for (uint i = 0; i < indices.Length; i++)
				if (indices[i] != i)
					return (false);

			return (true);
		}

		#endregion

		#region Logging

		/// <summary>
		/// Logger of this class.
		/// </summary>
		private static readonly ILogger sLog = Log.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

		#endregion

		#region ColladaXmlSerializable Overrides

		/// <summary>
		/// Read the attribute of the current XML serializable.
		/// </summary>
		/// <param name="xmlReader">
		/// A <see cref="XmlReader"/> used for reading XML element attributes.
		/// </param>
		/// <remarks>
		/// <para>
		/// This method is called only in the case there are effectively attributes on XML element. Don't expect that this
		/// method will be called even when the attributes count is zero. This is very important, since the design doesn't
		/// allow object initialization using this method.
		/// </para>
		/// </remarks>
		public override void ReadAttributes(XmlReader xmlReader)
		{
			if (xmlReader == null)
				throw new ArgumentNullException("xmlReader");

			string attribute;

			if ((attribute = xmlReader[ColladaAttributeCount]) == null)
				throw new InvalidOperationException("required attribute " + ColladaAttributeCount);
			Count = UInt32.Parse(attribute);

			if ((attribute = xmlReader[ColladaAttributeName]) != null)
				Name = attribute;
			if ((attribute = xmlReader[ColladaAttributeMaterial]) != null)
				Material = attribute;
		}

		/// <summary>
		/// Read an element of the current XML serializable.
		/// </summary>
		/// <param name="elementName">
		/// A <see cref="System.String"/> that specify the element name.
		/// </param>
		/// <param name="xmlReader">
		/// A <see cref="XmlReader"/> used for reading child element. It is positioned at the start element.
		/// </param>
		/// <remarks>
		/// <para>
		/// This method is responsible to read the start element of the nested element.
		/// </para>
		/// <para>
		/// This method is resposible to read all children elements till the corresponding end element, without reading it. In the
		/// case this condition is not satisfied, the XML parsing will be aborted.
		/// </para>
		/// </remarks>
		public override void ReadElement(string elementName, XmlReader xmlReader)
		{
			if (elementName == null)
				throw new ArgumentNullException("elementName");
			if (xmlReader == null)
				throw new ArgumentNullException("xmlReader");

			if (elementName == ColladaElementInput) {
				ColladaInputLocalOffset colladaInput = new ColladaInputLocalOffset(this);
				colladaInput.ReadXml(xmlReader);
				Inputs.Add(colladaInput);
			} else if (elementName == ColladaElementExtra) {
				ColladaExtra colladaExtra = new ColladaExtra(this);
				colladaExtra.ReadXml(xmlReader);
				Extras.Add(colladaExtra);
			} else
				base.ReadElement(elementName, xmlReader);
		}

		/// <summary>
		/// Collada 'geometry' attribute name (1.4).
		/// </summary>
		private const string ColladaAttributeName = "name";

		/// <summary>
		/// Collada 'geometry' attribute name (1.4).
		/// </summary>
		private const string ColladaAttributeCount = "count";

		/// <summary>
		/// Collada 'geometry' attribute name (1.4).
		/// </summary>
		private const string ColladaAttributeMaterial = "material";

		/// <summary>
		/// Collada 'geometry' child element name (1.4).
		/// </summary>
		private const string ColladaElementInput = "input";

		/// <summary>
		/// Collada 'geometry' child element name (1.4).
		/// </summary>
		protected const string ColladaElementP = "p";

		/// <summary>
		/// Collada 'geometry' child element name (1.4).
		/// </summary>
		private const string ColladaElementExtra = "extra";

		#endregion
	}

	/// <summary>
	/// The lines element provides the information needed to bind vertex attributes together and then 
	/// organize those vertices into individual lines. Each line described by the mesh has two vertices. 
	/// The first line is formed from first and second vertices. The second line is formed from the 
	/// third and fourth vertices and so on.
	/// </summary>
	public class ColladaLines : ColladaCollation
	{
		#region Constructors

		/// <summary>
		/// Construct a ColladaLines.
		/// </summary>
		/// <param name="parent">
		/// The parent element containing this ColladaLines.
		/// </param>
		public ColladaLines(ColladaXmlSerializable parent) : base(parent)
		{
			
		}

		#endregion

		#region Lines Information

		/// <summary>
		/// The p element may occur once.
		/// </summary>
		public uint[] Primitive;

		#endregion

		#region Logging

		/// <summary>
		/// Logger of this class.
		/// </summary>
		private static readonly ILogger sLog = Log.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

		#endregion

		#region ColladaCollation Overrides

		/// <summary>
		/// Get a vertex array object corresponding to this collation of geometric elements.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="ColladaCollationContext"/> that specify the parameters available for the collation.
		/// </param>
		/// <returns>
		/// It returns a <see cref="VertexArrayObject"/> containing vertex arrays and elements.
		/// </returns>
		internal override VertexArrayObject GetArrayObject(ColladaCollationContext ctx)
		{
			throw new NotImplementedException();
		}

		#endregion
	}

	/// <summary>
	/// The linestrips element provides the information needed to bind vertex attributes together and 
	/// then organize those vertices into connected line-strips. Each line-strip described by the mesh 
	/// has an arbitrary number of vertices. Each line segment within the line-strip is formed from the 
	/// current vertex and the preceding vertex.
	/// </summary>
	public class ColladaLineStrips : ColladaCollation
	{
		#region Constructors

		/// <summary>
		/// Construct a ColladaLineStrips.
		/// </summary>
		/// <param name="parent">
		/// The parent element containing this ColladaLineStrips.
		/// </param>
		public ColladaLineStrips(ColladaXmlSerializable parent) : base(parent)
		{
			
		}

		#endregion

		#region Line Strips Information

		/// <summary>
		/// The p element may occur any number of times.
		/// </summary>
		public readonly List<uint[]> Primitives = new List<uint[]>();

		#endregion

		#region Logging

		/// <summary>
		/// Logger of this class.
		/// </summary>
		private static readonly ILogger sLog = Log.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

		#endregion

		#region ColladaCollation Overrides

		/// <summary>
		/// Get a vertex array object corresponding to this collation of geometric elements.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="ColladaCollationContext"/> that specify the parameters available for the collation.
		/// </param>
		/// <returns>
		/// It returns a <see cref="VertexArrayObject"/> containing vertex arrays and elements.
		/// </returns>
		internal override VertexArrayObject GetArrayObject(ColladaCollationContext ctx)
		{
			throw new NotImplementedException();
		}

		#endregion
	}

	/// <summary>
	/// The polygons element provides the information needed to bind vertex attributes together and 
	/// then organize those vertices into individual polygons. The polygons described can contain 
	/// arbitrary numbers of vertices. These polygons may be self intersecting and may also contain holes.
	/// </summary>
	public class ColladaPolygons : ColladaCollation
	{
		#region Constructors

		/// <summary>
		/// Construct a ColladaPolygons.
		/// </summary>
		/// <param name="parent">
		/// The parent element containing this ColladaPolygons.
		/// </param>
		public ColladaPolygons(ColladaXmlSerializable parent) : base(parent)
		{
			
		}

		#endregion

		#region Polygons Information

		/// <summary>
		/// The p element may occur any number of times.
		/// </summary>
		public readonly List<uint[]> Primitives = new List<uint[]>();

		#endregion

		#region Logging

		/// <summary>
		/// Logger of this class.
		/// </summary>
		private static readonly ILogger sLog = Log.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

		#endregion

		#region ColladaCollation Overrides

		/// <summary>
		/// Get a vertex array object corresponding to this collation of geometric elements.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="ColladaCollationContext"/> that specify the parameters available for the collation.
		/// </param>
		/// <returns>
		/// It returns a <see cref="VertexArrayObject"/> containing vertex arrays and elements.
		/// </returns>
		internal override VertexArrayObject GetArrayObject(ColladaCollationContext ctx)
		{
			throw new NotImplementedException();
		}

		#endregion
	}

	/// <summary>
	/// The polylist element provides the information needed to bind vertex attributes together and 
	/// then organize those vertices into individual polygons. The polygons described in polylist can 
	/// contain arbitrary numbers of vertices. Unlike the polygons element, the polylist element cannot 
	/// contain polygons with holes.
	/// </summary>
	public class ColladaPolygonList : ColladaCollation
	{
		#region Constructors

		/// <summary>
		/// Construct a ColladaPolygonList.
		/// </summary>
		/// <param name="parent">
		/// The parent element containing this ColladaPolygonList.
		/// </param>
		public ColladaPolygonList(ColladaXmlSerializable parent) : base(parent)
		{
			
		}

		#endregion

		#region Polygons List Information

		/// <summary>
		/// The vcount element contains a list of integers describing the number of sides for each polygon 
		/// described by the polylist element. The vcount element may occur once.
		/// </summary>
		public uint[] VerticesCount;

		/// <summary>
		/// The p element may occur once.
		/// </summary>
		public uint[] Primitive;

		#endregion

		#region Logging

		/// <summary>
		/// Logger of this class.
		/// </summary>
		private static readonly ILogger sLog = Log.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

		#endregion

		#region ColladaCollation Overrides

		/// <summary>
		/// Read an element of the current XML serializable.
		/// </summary>
		/// <param name="elementName">
		/// A <see cref="System.String"/> that specify the element name.
		/// </param>
		/// <param name="xmlReader">
		/// A <see cref="XmlReader"/> used for reading child element. It is positioned at the start element.
		/// </param>
		/// <remarks>
		/// <para>
		/// This method is responsible to read the start element of the nested element.
		/// </para>
		/// <para>
		/// This method is resposible to read all children elements till the corresponding end element, without reading it. In the
		/// case this condition is not satisfied, the XML parsing will be aborted.
		/// </para>
		/// </remarks>
		public override void ReadElement(string elementName, XmlReader xmlReader)
		{
			if (elementName == null)
				throw new ArgumentNullException("elementName");
			if (xmlReader == null)
				throw new ArgumentNullException("xmlReader");

			if (elementName == ColladaElementP) {
				Primitive = XmlHelper.ReadXmlListOfUInts(xmlReader);
			} else if (elementName == ColladaElementVerticesCount) {
				VerticesCount = XmlHelper.ReadXmlListOfUInts(xmlReader);
			} else
				base.ReadElement(elementName, xmlReader);
		}

		/// <summary>
		/// Get a vertex array object corresponding to this collation of geometric elements.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="ColladaCollationContext"/> that specify the parameters available for the collation.
		/// </param>
		/// <returns>
		/// It returns a <see cref="VertexArrayObject"/> containing vertex arrays and elements.
		/// </returns>
		internal override VertexArrayObject GetArrayObject(ColladaCollationContext ctx)
		{
			if (ctx == null)
				throw new ArgumentNullException("ctx");

			VertexArrayObject vertexArray = new VertexArrayObject();

			// Determine primitive mode
			Render.Primitive primitiveMode;
			uint minVertices = UInt32.MaxValue, maxVertices = UInt32.MinValue;

			Array.ForEach(VerticesCount, delegate(uint p) {
				minVertices = Math.Min(p, minVertices);
			    maxVertices = Math.Max(p, maxVertices);
			});

			sLog.Debug("Polygon list collation '{0}' defines polygons with vertices from {1} to {2}", Name ?? String.Empty, minVertices, maxVertices);

			if (minVertices == maxVertices) {
				switch (maxVertices) {
					case 1:
						primitiveMode = Render.Primitive.Point;
						break;
					case 2:
						primitiveMode = Render.Primitive.Line;
						break;
					case 3:
						primitiveMode = Render.Primitive.Triangle;
						break;
					default:
						// Triangulate
						primitiveMode = Render.Primitive.Triangle;
						break;
				}
			} else {
				// Polygon list with mixed point, line and triangles are bad
				if (minVertices < 3)
					throw new NotSupportedException("ambigous polylist vertices count");

				primitiveMode = Render.Primitive.Triangle;		// Mixed triangles and quads - Triangulate
			}

			sLog.Debug("Polygon list collation '{0}' primitive is {1}", Name ?? String.Empty, primitiveMode);

			// Determine vertex array state (it can be used for further processing, i.e. lighting)
			CompileVertexArrayState(ctx, vertexArray);

			// Define element buffer
			if (Inputs.Count > 0) {
				uint maxInputsOffset = 0;

				if (IsSharedElementBuffer() == false) {
					bool hasNormals = Inputs.Exists(delegate(ColladaInputLocalOffset input) {
                		return (ColladaInputSemantic.ConvertSemantic(input.Semantic) == VertexArraySemantic.Normal);
                	});

					// Indices are interleaved in the indices array: extract data deferenced by index in separate buffers

					// Determine maximum offset (trust about no holes?)
					Inputs.ForEach(delegate(ColladaInputLocalOffset input) { maxInputsOffset = Math.Max(maxInputsOffset, input.Offset); });

					sLog.Debug("Polygon list collation '{0}' has {1} inputs not sharing element buffer (max. offset: {2})", Name ?? String.Empty, Inputs.Count, maxInputsOffset);

					// Extract data referenced by indices
					foreach (ColladaInputLocalOffset input in Inputs) {
						ArrayBufferObject bufferObject, postBufferObject;

						if (ctx.ArrayBuffers.TryGetValue(GetFragmentName(input.Source), out bufferObject) == false)
							throw new InvalidOperationException(String.Format("unable to resolve source {0}", input.Source));

						// Construct the final array buffer from indices
						postBufferObject = new ArrayBufferObject(bufferObject.ArrayType, BufferObject.Hint.StaticCpuDraw);
						postBufferObject.Copy(bufferObject, Primitive, VerticesCount, input.Offset, maxInputsOffset + 1);

						string semantic = ColladaInputSemantic.ConvertSemantic(input.Semantic);

						switch (semantic) {
							case VertexArraySemantic.Position:
								{
									if (vertexArray.StateSet.IsDefinedState(MaterialState.StateId) && (hasNormals == false)) {
										uint[] positionPrimitive = new uint[Primitive.Length / (maxInputsOffset + 1)];
										uint index = 0;

										for (uint i = input.Offset; i < Primitive.Length; i += (maxInputsOffset + 1)) {
											positionPrimitive[index++] = Primitive[i];
										}

										// Smooth normals
										ElementBufferObject<uint> fictiveElementBuffer = new ElementBufferObject<uint>(BufferObject.Hint.StaticCpuDraw);
										fictiveElementBuffer.Define(positionPrimitive);
										
										ElementBufferObject<uint> postElementObject;

										postElementObject = new ElementBufferObject<uint>(BufferObject.Hint.StaticCpuDraw);
										postElementObject.Triangulate(fictiveElementBuffer, VerticesCount);

										VertexArrayObject vao = new VertexArrayObject();
										vao.SetElementArray(Render.Primitive.Triangle, postElementObject);
										vao.SetArray(semantic, bufferObject);

										vao.GenerateNormals();

										bufferObject = vao.GetArray(VertexArraySemantic.Normal);

										ArrayBufferObject postNormalBufferObject = new ArrayBufferObject(bufferObject.ArrayType, BufferObject.Hint.StaticCpuDraw);
										postNormalBufferObject.Copy(bufferObject, Primitive, VerticesCount, input.Offset, maxInputsOffset + 1);

										vertexArray.SetArray(VertexArraySemantic.Normal, postNormalBufferObject);
									}
								}
								break;
							case VertexArraySemantic.TexCoord:
								semantic = String.Format("{0}[{1}]", semantic, input.Set);
								break;
						}

						sLog.Debug("Polygon list collation '{0}' input {1} has semantic {2} ({3} vertices)", Name ?? String.Empty, input.Source, semantic, postBufferObject.ItemCount);

						vertexArray.SetArray(semantic, postBufferObject);
					}
					// No actual element buffer: draw arrays sequentially
					vertexArray.SetElementArray(primitiveMode);
				} else {
					
					// All inputs are indexed with the same indices: define a common element buffer, if necessary

					sLog.Debug("Polygon list collation '{0}' has {1} inputs sharing the same element buffer", Name ?? String.Empty, Inputs.Count);

#if  false
					ArrayBufferObject bufferObject, postBufferObject;

					if (ctx.ArrayBuffers.TryGetValue(GetFragmentName(Inputs[0].Source), out bufferObject) == false)
						throw new InvalidOperationException(String.Format("unable to resolve source {0}", Inputs[0].Source));

					// Construct the final array buffer from indices
					postBufferObject = new ArrayBufferObject(bufferObject.ArrayType, BufferObject.Hint.StaticCpuDraw);
					postBufferObject.Copy(bufferObject, Primitive, VerticesCount, 0, 1);

					string semantic = ColladaInputSemantic.ConvertSemantic(Inputs[0].Semantic);

					switch (semantic) {
						case VertexArraySemantic.TexCoord:
							semantic = String.Format("{0}[{1}]", semantic, Inputs[0].Set);
							break;
					}

					sLog.Debug("Polygon list collation '{0}' input {1} has semantic {2} ({3} vertices)", Name ?? String.Empty, Inputs[0].Source, semantic, postBufferObject.ItemCount);

					vertexArray.SetArray(semantic, postBufferObject);

					// No actual element buffer: draw arrays sequentially
					vertexArray.SetElementArray(primitiveMode);
#endif

					ArrayBufferObject bufferObject;

					if (ctx.ArrayBuffers.TryGetValue(GetFragmentName(Inputs[0].Source), out bufferObject) == false)
						throw new InvalidOperationException(String.Format("unable to resolve source {0}", Inputs[0].Source));

					string semantic = ColladaInputSemantic.ConvertSemantic(Inputs[0].Semantic);

					switch (semantic) {
						case VertexArraySemantic.TexCoord:
							semantic = String.Format("{0}[{1}]", semantic, Inputs[0].Set);
							break;
					}

					sLog.Debug("Polygon list collation '{0}' input {1} has semantic {2} ({3} vertices)", Name ?? String.Empty, Inputs[0].Source, semantic, bufferObject.ItemCount);

					vertexArray.SetArray(semantic, bufferObject);

					// Actual element buffer: draw arrays indexed
					ElementBufferObject<uint> elementObject, postElementObject;

					elementObject = new ElementBufferObject<uint>(Primitive, BufferObject.Hint.StaticCpuDraw);

					postElementObject = new ElementBufferObject<uint>(BufferObject.Hint.StaticCpuDraw);
					postElementObject.Triangulate(elementObject, VerticesCount);

					vertexArray.SetElementArray(primitiveMode, postElementObject);

					// Ensuring normals if lighting is required
					// Particoular: here the normals are smooth, since each vertex share the normals with each triangle sharing
					// the same vertex
					if ((vertexArray.StateSet.IsDefinedState(MaterialState.StateId)) && (vertexArray.HasArray(VertexArraySemantic.Normal) == false))
						vertexArray.GenerateNormals();
				}
			} else
				throw new NotSupportedException("collation with not inputs");

			return (vertexArray);
		}

		/// <summary>
		/// Collada 'geometry' child element name (1.4).
		/// </summary>
		private const string ColladaElementVerticesCount = "vcount";

		#endregion
	}

	/// <summary>
	/// The triangles element provides the information needed to bind vertex attributes together and 
	/// then organize those vertices into individual triangles.	Each triangle described by the mesh has 
	/// three vertices. The first triangle is formed from the first, second, and third vertices. The 
	/// second triangle is formed from the fourth, fifth, and sixth vertices, and so on.
	/// </summary>
	public class ColladaTriangles : ColladaCollation
	{
		#region Constructors

		/// <summary>
		/// Construct a ColladaTriangles.
		/// </summary>
		/// <param name="parent">
		/// The parent element containing this ColladaTriangles.
		/// </param>
		public ColladaTriangles(ColladaXmlSerializable parent) : base(parent)
		{
			
		}

		#endregion

		#region Triangles Information

		/// <summary>
		/// The p element may occur once.
		/// </summary>
		public uint[] Primitive;

		#endregion

		#region Logging

		/// <summary>
		/// Logger of this class.
		/// </summary>
		private static readonly ILogger sLog = Log.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

		#endregion

		#region ColladaCollation Overrides

		/// <summary>
		/// Read an element of the current XML serializable.
		/// </summary>
		/// <param name="elementName">
		/// A <see cref="System.String"/> that specify the element name.
		/// </param>
		/// <param name="xmlReader">
		/// A <see cref="XmlReader"/> used for reading child element. It is positioned at the start element.
		/// </param>
		/// <remarks>
		/// <para>
		/// This method is responsible to read the start element of the nested element.
		/// </para>
		/// <para>
		/// This method is resposible to read all children elements till the corresponding end element, without reading it. In the
		/// case this condition is not satisfied, the XML parsing will be aborted.
		/// </para>
		/// </remarks>
		public override void ReadElement(string elementName, XmlReader xmlReader)
		{
			if (elementName == null)
				throw new ArgumentNullException("elementName");
			if (xmlReader == null)
				throw new ArgumentNullException("xmlReader");

			if (elementName == ColladaElementP) {
				Primitive = XmlHelper.ReadXmlListOfUInts(xmlReader);
			} else
				base.ReadElement(elementName, xmlReader);
		}

		/// <summary>
		/// Get a vertex array object corresponding to this collation of geometric elements.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="ColladaCollationContext"/> that specify the parameters available for the collation.
		/// </param>
		/// <returns>
		/// It returns a <see cref="VertexArrayObject"/> containing vertex arrays and elements.
		/// </returns>
		internal override VertexArrayObject GetArrayObject(ColladaCollationContext ctx)
		{
			if (ctx == null)
				throw new ArgumentNullException("ctx");

			VertexArrayObject vertexArray = new VertexArrayObject();
			ElementBufferObject<uint> vertexElements = new ElementBufferObject<uint>(BufferObject.Hint.StaticCpuDraw);

			// Define element buffer
			if (Inputs.Count > 1) {
				uint maxInputsOffset = 0, elementCursor = 0;

				if (IsSharedElementBuffer() == false) {

					// Indices are interleaved in the indices array: extract data deferenced by index in separate buffers

					// Determine maximum offset (trust about no holes?)
					Inputs.ForEach(delegate(ColladaInputLocalOffset input) { maxInputsOffset = Math.Max(maxInputsOffset, input.Offset); });

					// Extract data referenced by indices
					foreach (ColladaInputLocalOffset input in Inputs) {
						ArrayBufferObject bufferObject = null, postBufferObject;

						if (ctx.ArrayBuffers.TryGetValue(GetFragmentName(input.Source), out bufferObject) == false)
							throw new InvalidOperationException(String.Format("unable to resolve source {0}", input.Source));

						// Construct the final array buffer from indices
						postBufferObject = new ArrayBufferObject(bufferObject.ArrayType, BufferObject.Hint.StaticCpuDraw);
						postBufferObject.Copy(bufferObject, Primitive, Count * 3, input.Offset, maxInputsOffset + 1);

						string semantic = ColladaInputSemantic.ConvertSemantic(input.Semantic);

						switch (semantic) {
							case VertexArraySemantic.TexCoord:
								semantic = String.Format("{0}[{1}]", semantic, input.Set);
								break;
						}

						vertexArray.SetArray(semantic, postBufferObject);
					}
					// No actual element buffer: draw arrays sequentially
					vertexArray.SetElementArray(Render.Primitive.Triangle);
				} else {
					
					// All inputs are indexed with the same indices: define a common element buffer, if necessary

					if (IsSimpleElementBuffer(Primitive) == false) {
						vertexElements.Define(Primitive);
						vertexArray.SetElementArray(Render.Primitive.Triangle, vertexElements);
					} else
						vertexArray.SetElementArray(Render.Primitive.Triangle);
				}
			} else
				vertexElements.Define(Primitive);

			// Determine vertex array state
			CompileVertexArrayState(ctx, vertexArray);

			return (vertexArray);
		}

		#endregion
	}

	/// <summary>
	/// The trifans element provides the information needed to bind vertex attributes together and then 
	/// organize those vertices into connected triangles. Each triangle described by the mesh has three 
	/// vertices. The first triangle is formed from first, second, and third vertices. Each subsequent 
	/// triangle is formed from the current vertex, reusing the first and the previous vertices.
	/// </summary>
	public class ColladaTrangleFans : ColladaCollation
	{
		#region Constructors

		/// <summary>
		/// Construct a ColladaTrangleFans.
		/// </summary>
		/// <param name="parent">
		/// The parent element containing this ColladaTrangleFans.
		/// </param>
		public ColladaTrangleFans(ColladaXmlSerializable parent) : base(parent)
		{
			
		}

		#endregion

		#region Triangle Fans Information

		/// <summary>
		/// The trifans element may have any number of p elements.
		/// </summary>
		public readonly List<uint[]> Primitives = new List<uint[]>();

		#endregion

		#region Logging

		/// <summary>
		/// Logger of this class.
		/// </summary>
		private static readonly ILogger sLog = Log.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

		#endregion

		#region ColladaCollation Overrides

		/// <summary>
		/// Get a vertex array object corresponding to this collation of geometric elements.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="ColladaCollationContext"/> that specify the parameters available for the collation.
		/// </param>
		/// <returns>
		/// It returns a <see cref="VertexArrayObject"/> containing vertex arrays and elements.
		/// </returns>
		internal override VertexArrayObject GetArrayObject(ColladaCollationContext ctx)
		{
			throw new NotImplementedException();
		}

		#endregion
	}

	/// <summary>
	/// The tristrips element provides the information needed to bind vertex attributes together and then 
	/// organize those vertices into connected triangles. Each triangle described by the mesh has three 
	/// vertices. The first triangle is formed from first, second, and third vertices. Each subsequent 
	/// triangle is formed from the current vertex, reusing the previous two vertices.
	/// </summary>
	public class ColladaTriangleStrips : ColladaCollation
	{
		#region Constructors

		/// <summary>
		/// Construct a ColladaTriangleStrips.
		/// </summary>
		/// <param name="parent">
		/// The parent element containing this ColladaTriangleStrips.
		/// </param>
		public ColladaTriangleStrips(ColladaXmlSerializable parent) : base(parent)
		{
			
		}

		#endregion

		#region Triangle Strips Information

		/// <summary>
		/// The tristrips element may have any number of p elements.
		/// </summary>
		public readonly List<uint[]> Primitives = new List<uint[]>();

		#endregion

		#region Logging

		/// <summary>
		/// Logger of this class.
		/// </summary>
		private static readonly ILogger sLog = Log.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

		#endregion

		#region ColladaCollation Overrides

		/// <summary>
		/// Get a vertex array object corresponding to this collation of geometric elements.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="ColladaCollationContext"/> that specify the parameters available for the collation.
		/// </param>
		/// <returns>
		/// It returns a <see cref="VertexArrayObject"/> containing vertex arrays and elements.
		/// </returns>
		internal override VertexArrayObject GetArrayObject(ColladaCollationContext ctx)
		{
			throw new NotImplementedException();
		}

		#endregion
	}

	/// <summary>
	/// The vertices element declares the attributes and identity of mesh-vertices. The vertices element
	/// describes mesh-vertices in a mesh geometry. The mesh-vertices represent the position (identity) 
	/// of the vertices comprising the mesh and other vertex attributes that are invariant to tessellation.
	/// </summary>
	public class ColladaVertices : ColladaXmlSerializable
	{
		#region Constructors

		/// <summary>
		/// Construct a ColladaVertices.
		/// </summary>
		/// <param name="parent">
		/// The parent element containing this ColladaVertices.
		/// </param>
		public ColladaVertices(ColladaXmlSerializable parent) : base(parent)
		{
			
		}

		#endregion

		#region Vertices Information

		/// <summary>
		/// The id attribute is a text string containing the unique identifier of this element. This 
		/// value must be unique within the instance document. Required attribute.
		/// </summary>
		public string Id;

		/// <summary>
		/// The name attribute is the text string name of this element. Optional attribute.
		/// </summary>
		public string Name;

		/// <summary>
		/// The input element must occur at least one time. These inputs are local inputs.
		/// </summary>
		public List<ColladaInputLocal> Inputs = new List<ColladaInputLocal>();

		/// <summary>
		/// The extra element may appear any number of times.
		/// </summary>
		public List<ColladaExtra>  Extras = new List<ColladaExtra>();

		#endregion

		#region Logging

		/// <summary>
		/// Logger of this class.
		/// </summary>
		private static readonly ILogger sLog = Log.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

		#endregion

		#region ColladaXmlSerializable Overrides

		/// <summary>
		/// Read the attribute of the current XML serializable.
		/// </summary>
		/// <param name="xmlReader">
		/// A <see cref="XmlReader"/> used for reading XML element attributes.
		/// </param>
		/// <remarks>
		/// <para>
		/// This method is called only in the case there are effectively attributes on XML element. Don't expect that this
		/// method will be called even when the attributes count is zero. This is very important, since the design doesn't
		/// allow object initialization using this method.
		/// </para>
		/// </remarks>
		public override void ReadAttributes(XmlReader xmlReader)
		{
			if (xmlReader == null)
				throw new ArgumentNullException("xmlReader");

			string attribute;

			if ((attribute = xmlReader[ColladaAttributeId]) == null)
				throw new InvalidOperationException("required attribute " + ColladaAttributeId);
			Id = attribute;

			if ((attribute = xmlReader[ColladaAttributeName]) != null)
				Name = attribute;
		}

		/// <summary>
		/// Read an element of the current XML serializable.
		/// </summary>
		/// <param name="elementName">
		/// A <see cref="System.String"/> that specify the element name.
		/// </param>
		/// <param name="xmlReader">
		/// A <see cref="XmlReader"/> used for reading child element. It is positioned at the start element.
		/// </param>
		/// <remarks>
		/// <para>
		/// This method is responsible to read the start element of the nested element.
		/// </para>
		/// <para>
		/// This method is resposible to read all children elements till the corresponding end element, without reading it. In the
		/// case this condition is not satisfied, the XML parsing will be aborted.
		/// </para>
		/// </remarks>
		public override void ReadElement(string elementName, XmlReader xmlReader)
		{
			if (elementName == null)
				throw new ArgumentNullException("elementName");
			if (xmlReader == null)
				throw new ArgumentNullException("xmlReader");

			if (elementName == ColladaElementInput) {
				ColladaInputLocal colladaInput = new ColladaInputLocal(this);
				colladaInput.ReadXml(xmlReader);
				Inputs.Add(colladaInput);
			} else if (elementName == ColladaElementExtra) {
				ColladaExtra colladaExtra = new ColladaExtra(this);
				colladaExtra.ReadXml(xmlReader);
				Extras.Add(colladaExtra);
			} else
				base.ReadElement(elementName, xmlReader);
		}

		/// <summary>
		/// Collada 'geometry' attribute name (1.4).
		/// </summary>
		private const string ColladaAttributeId = "id";

		/// <summary>
		/// Collada 'geometry' attribute name (1.4).
		/// </summary>
		private const string ColladaAttributeName = "name";

		/// <summary>
		/// Collada 'geometry' child element name (1.4).
		/// </summary>
		private const string ColladaElementInput = "input";

		/// <summary>
		/// Collada 'geometry' child element name (1.4).
		/// </summary>
		private const string ColladaElementExtra = "extra";

		#endregion
	}
}
